/***************************************************************************
 *   Copyright (C) 2005 by xihe   *
 *   xihels@163.com                                                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "QQClient.h"
#include "LoginToken.h"
#include "Login.h"
#include "ClusterOperateReply.h"
#include "ChangeStatus.h"
#include <stdexcept>
#include "md5.h"
#include "RegisterInPacket.h"
#include "assert.h"
using std::runtime_error;

uint8_t QQClient::iniKey[16] = {
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
};

/******************************************************/
void showbuf(const uint8_t* buf, int len)
{
    printf ("00  ");
    for (int i = 1; i < 33; i++)
	printf("%02d ", i);
    printf("\n");
    for (int i = 0; i < len; i ++ ) {
	if (!(i % 32)) printf("\n%02d  ", i / 32 + 1);
	printf("%02x ", buf[i]);
    }
    printf("\n");
}
/******************************************************/

///////////////////////////////////////////////////////////
// 注册消息包类的创建函数,使QQClinet类可以由命令字节,动态创建出相应的包.

namespace {
    RegisterInPacket P1(QQ_CMD_REQUEST_LOGIN_TOKEN, LoginTokenReplyPacket::create);
    RegisterInPacket P2(QQ_CMD_LOGIN, LoginReplyPacket::create);
    RegisterInPacket P3(QQ_CMD_CLUSTER_CMD, ClusterOperateReply::create);
}
///////////////////////////////////////////////////////////

// 当前的连接状态,
// 在线
#define CONNECT_STATUS	    0   
// 需要开始检测(所有包发送前都增加检测数)
#define START_DETECT_STATUS 5
// 从这里开始，只是心跳包发送前增加检测数
#define MIDDLE_DETECT_STATUS 10
// 结束检测(可以确定是掉线了)
#define END_DETECT_STATUS   12

// 线程运行标志
// 停止状态
#define STOP_STATUS	0
// 运行状态
#define RUN_STATUS	1


QQClient::QQClient(const QQUser& user_) :
    user(user_), 
    recvThread(this, &QQClient::recvPacket),
    RUNNING(STOP_STATUS),
    NETSTATUS(OFFINE_STATUS),
    connecting(CONNECT_STATUS),
    tryLoginCount(0),
    face(0xffff)
{

}

QQClient::~QQClient(){}

void QQClient::doMd5(uint8_t* in, int len, uint8_t* out)
{
    md5_state_t ctx;

    md5_init(&ctx);
    md5_append(&ctx, (md5_byte_t *)in, len);
    md5_finish(&ctx, (md5_byte_t *)out);
}


void QQClient::onUnknow(const UnknowPacket& packet)
{
    switch (packet.getCommand()) {
	case QQ_CMD_KEEP_ALIVE:
	    return;
	default:
	    doUnknow(packet);
    }
}


// 取得登录令牌
void QQClient::onLoginToken(const LoginTokenReplyPacket& packet)
{

    // 接收登录令令牌包

    if (0x00 != packet.getReplyCode())
	throw (runtime_error("inceptLoginToken(): error replyCode."));
    loginToken = packet.getLoginToken();

    // 暂时把sessionKey设成iniKey 以便发送登录包
    memcpy(sessionKey, iniKey, QQ_KEY_LENGTH);


    // 发送登录包  
    LoginPacket loginPacket(*this);
    sendLoginPacket(loginPacket);

}    

void QQClient::onLogin(const LoginReplyPacket& packet)
{
    srand( (unsigned)time( NULL ) );
    sequence = rand() & 0xffff;
    
    uint8_t fsbuf[QQ_KEY_LENGTH + 4];

    uint8_t replyCode = packet.getReplyCode();
    sockaddr_in sa;

    switch (replyCode) {
	case QQ_LOGIN_REPLY_OK:
	    writeBufData(sessionKey, packet.getSessionKey(), QQ_KEY_LENGTH);
	    writeBufDw(fsbuf, getUid());
	    memcpy(fsbuf + 4, sessionKey, QQ_KEY_LENGTH);
	    doMd5(fsbuf, QQ_KEY_LENGTH + 4, fileSessionKey);
	    
	    NETSTATUS = ONLINE_STATUS; // 设置网络状态标志为在线
	    tryLoginCount = 0;
	    doLoginOk();
	    break;
	case QQ_LOGIN_REPLY_PASSWORD_ERROR:
	    // 关闭连接
	    connect.close();

	    // 接收线程退出
	    RUNNING = STOP_STATUS;
	    recvThread.join();
	    
	    NETSTATUS = OFFINE_STATUS;// 设置网络状态标志为离线
	    tryLoginCount = 0;
	    
	    doLoginError(packet);
	    break;

	case QQ_LOGIN_REPLY_REDIRECT:

	    tryLoginCount = 0;
	    
	    // 设置重定向
	    sa.sin_family = AF_INET;
	    sa.sin_addr.s_addr = htonl(packet.getRedirectIp());
	    sa.sin_port = htons(packet.getRedirectPort());
	    connect.setSockaddr(sa);

	    RUNNING = STOP_STATUS;
	    // 关闭连接
	    connect.close();
	    // 接收线程退出
	    recvThread.join();

	    // 重新打开连接
	    connect.open();
	    RUNNING = RUN_STATUS;
	    // 启动接收线程
	    recvThread.start();

	    // 发送登录包  
	    LoginPacket loginPacket(*this);
	    sendLoginPacket(loginPacket);
	    break;
    }

} 

void QQClient::login() 
{
    NETSTATUS = LOGIN_STATUS;

    sockaddr_in sa;

    connect.setServerIp(user.getServer());
    connect.setServerPort(user.getPort());
    connect.setTcp(user.isTcp());

    // 打开连接
    connect.open();
    RUNNING = RUN_STATUS;
    // 启动接收线程
    recvThread.start();

    uint8_t logBuf[100];
    LoginTokenPacket rlt(*this);
    uint8_t* cur = logBuf;

    if (user.isTcp())
	cur += 2;
    cur += rlt.putHead(cur);
    cur += rlt.putBody(cur);
    *cur++ = QQ_TAIL_BASIC_FAMILY;

    int packlen = cur - logBuf;

    if (user.isTcp())
	writeBufW(logBuf, packlen);

    try {
	connect.send(logBuf, packlen);
    }
    catch (...){
	RUNNING = STOP_STATUS;
	NETSTATUS = OFFINE_STATUS;
	// 关闭连接
	connect.close();
	// 结束接收线程
	recvThread.join();
	throw;
    }

}

void QQClient::logout() 
{
    if (RUN_STATUS == RUNNING) {
	RUNNING = STOP_STATUS; 

	try {
	    // 发送登出包4次
	    LogoutPacket packet(*this);
	    for (int i = 0; i < 4; i++) 
		sendPacket(packet);
	}
	catch (const exception& e) {
	    fprintf(stderr, "QQClient::logout error : %s\n", e.what());
	}

	// 关闭连接
	connect.close();
	recvThread.join();
    }
    NETSTATUS = OFFINE_STATUS;// 设置网络状态标志为离线
}

int QQClient::tryLogin()
{
    if (LOGIN_STATUS == NETSTATUS) { // 检测登录
	int result = STATUS_OK;
	switch (tryLoginCount) {
	    case 0:
		tryLoginCount++;
		break;
	    case 1:
	    case 2:
		tryLoginCount++;
		logout();
		try {
		    login();
		}
		catch (const exception& e) {
		    fprintf(stderr, "QQClient::keepAlive error : %s\n", e.what());
		    result = STATUS_ERROR;
		    tryLoginCount = 0;
		}
		break;
	    default:
		logout();
		result = STATUS_ERROR;
		tryLoginCount = 0;
	}
	return result;
    } 

}

int QQClient::keepAlive()
{
    // 每过60秒向服务器发一个KeepAlive包
    // 每4秒检查一次重发队列

    ////////////////

    time_t curtime;
    bool isempty = true;
    bool needsend = false;
    // 遍历一次重发队列
    curtime = time(NULL);

    AGAIN_SEND_QUEUE::iterator first = againSendQueue.begin();
    AGAIN_SEND_QUEUE::iterator last  = againSendQueue.end();

    isempty = true;
    needsend = false;

    for (; first != last; ++first) {

	if (NULL == (*first))      // 有些项可能是NULL,要检测一下
	    continue;		   

	if ((curtime - (*first)->sendTime()) < 4 ) { 
	    isempty = false;	    // 上次发送后还没有超过4秒
	    continue;		    // 设置重发队列为不空
	}			    // 不清空队列


	if (0 >= ((*first)->stepSendCount())) { // 再次重发数等于0,放弃
	    delete *first;
	    *first = NULL;
	} else {  // 重新发送, 再次发送次数减1
	    (*first)->setSequence(stepSequence());
	    (*first)->sendTime(time(NULL));
	    // 发送...
	    sendPacket(*(*first));
	    isempty = false;   // 设置标志,不清空队列
	}

    }

    if (isempty && !againSendQueue.empty()) {
	againSendQueue.erase(againSendQueue.begin(), 
		againSendQueue.end());	// 如果重发队列空，清空队列
    }


    // 检测网络状态 
    if (START_DETECT_STATUS <= connecting) {
	if (END_DETECT_STATUS >= connecting) {
	    // 再次检测 网络状态
	    KeepAlivePacket keep(*this);
	    sendPacket(keep);
	    connecting++;
	} else {
	    // 网络已经断开(不断也关了它^_^)
	    // 设置网络状态标志为离线
	    NETSTATUS = OFFINE_STATUS;
	    RUNNING = STOP_STATUS;
	    // 关闭连接
	    connect.close();
	    // 接收线程 退出
	    recvThread.join();

	    doKeepAlive(STATUS_DISCONNECT);	
	    return STATUS_DISCONNECT;
	}
    }
    
    if ((curtime - keepAliveTime) > 60)  { // 60秒，发送一次心跳包
	keepAliveTime = curtime;

	doKeepAlive(STATUS_OK);	
	sendPacket(OutPacketPtr(new KeepAlivePacket(*this)));
	// 子类重载这个函数，用来做一些周期性工作，比如刷新好友列表等
    }
    return STATUS_OK;
}

void QQClient::againSendQueuePush(OutPacket* packet)
{
    packet->sendTime(time(NULL));
    againSendQueue.push_back(packet);
}

void QQClient::againSendQueueRemove(uint16_t sequence)
{

    AGAIN_SEND_QUEUE::iterator iter = 
	find_if(againSendQueue.begin(), againSendQueue.end(), CompareSequence(sequence));

    if (iter != againSendQueue.end()) {
	delete *iter;
	*iter = NULL;			
    }

}

void QQClient::sendPacket(OutPacketPtr packPtr) 
{
    // 本函数负责清理掉不需要重发的包
    // 

    if (connecting < MIDDLE_DETECT_STATUS)
	connecting++;		// 检测网络 

    sendPacket(*packPtr);
   // printf("this is the %d time\n",packPtr->stepSendCount());// 发送
    if ( packPtr->stepSendCount()) {
    //	printf("a packet add the againSendQueue\n");
	againSendQueuePush(packPtr.release());	// 加入重发队列
    }
}

void QQClient::sendLoginPacket(const LoginPacket& packet) 
{
    // 这个函数专门用来发送LoginPacket
    // 因为LoginPacket前面有16个字节不加密
    int encryptlen;

    uint8_t* cur   = sendBuf;
    uint8_t* encur = sendEncryptBuf;

    if (connect.isTcp())
	cur += 2;

    cur += packet.putHead(cur);
    cur += packet.putLoginHead(cur);

    int bodylen = packet.putBody(encur);

    if (!encrypt(encur, bodylen, cur, encryptlen, sessionKey))
	throw (runtime_error("sendPacket(LoginPacket) Encrypt error"));

    cur += encryptlen;
    *cur++ = QQ_TAIL_BASIC_FAMILY;

    encryptlen = cur - sendBuf;
    if (connect.isTcp())
	writeBufW(sendBuf, encryptlen);

    connect.send(sendBuf, encryptlen);

}

void QQClient::sendPacket(const OutPacket& packet) 
{
    int encryptlen;

    uint8_t* cur   = sendBuf;
    uint8_t* encur = sendEncryptBuf;

    if (connect.isTcp())
	cur += 2;
    cur += packet.putHead(cur);

    int bodylen = packet.putBody(encur);

    if (!encrypt(encur, bodylen, cur, encryptlen, sessionKey))
	throw (runtime_error("sendPacket(OutPacket) Encrypt error"));

    cur += encryptlen;
    *cur++ = QQ_TAIL_BASIC_FAMILY;

    encryptlen = cur - sendBuf;
    if (connect.isTcp())
	writeBufW(sendBuf, encryptlen);

    connect.send(sendBuf, encryptlen);

}


void* QQClient::recvPacket(void*) 
{
    uint8_t rBuf[MAX_PACKET_SIZE];
    uint8_t deRBuf[MAX_PACKET_SIZE];

    int buflen = 0;	// 数据长度
    //int headlen = 0;
    uint8_t* cur = NULL;
    uint8_t* decur = NULL;
    int decryptlen = 0;	// 明文长度
    int encryptlen = 0; // 密文长度

    uint16_t lastSeq = 0;	// 最后收到的包的sequence;

    InPacketPtr packPtr;


    while (RUNNING) {
	// 接收包；
	try {
	    buflen = connect.recv(rBuf); 

	    if (buflen < 16)
		continue;

	    // 收到数据，表明网络连接正常
	    connecting = CONNECT_STATUS;

	    buflen--;   //  去掉包的未部的一个字节，因为这个字节没有加密

	    if (QQ_HEADER_BASIC_FAMILY != Packet::getHeader(rBuf)) {
		continue;
	    }

	    memcpy(deRBuf, rBuf, QQ_HEADER_LENGTH_BASIC_FAMILY_IN);
	    // 解密包体
	    //
	    cur = rBuf + QQ_HEADER_LENGTH_BASIC_FAMILY_IN;
	    encryptlen = buflen - QQ_HEADER_LENGTH_BASIC_FAMILY_IN;
	    decryptlen = encryptlen;
	    decur =  deRBuf + QQ_HEADER_LENGTH_BASIC_FAMILY_IN;

	    if (!decrypt(cur, encryptlen, decur, decryptlen, sessionKey)) {
		if (!(decrypt(cur, encryptlen, decur, decryptlen, getPwkey()))) {
		    if (!decrypt(cur ,encryptlen, decur, decryptlen, iniKey)) {
			if (QQ_CMD_REQUEST_LOGIN_TOKEN == Packet::getCommand(rBuf))
			    //decur = cur;
			    memcpy(deRBuf, rBuf, buflen);
			else
			{
			    // 解密失败
			    //		    fprintf(stderr, "recvPacket() : decrypt fail.\n");
			    continue;
			}
		    }
		}
	    }

	    if (1 > decryptlen)
		continue;

	    packPtr.reset(DynamicCreateMap::getSelf().createP(
			Packet::getCommand(deRBuf), 
			deRBuf,
			decryptlen + QQ_HEADER_LENGTH_BASIC_FAMILY_IN));

	    doRecvPacket(packPtr.release());
	}
	catch (exception& e) {
	    fprintf(stderr, "RecvThread error : %s\n", e.what());
	}
    }
    return NULL;

}



